package com.ryskoo.framework.core.repository;

import com.ryskoo.framework.core.bean.BasicPO;
import com.ryskoo.framework.core.bean.QueryConditionEnum;
import com.ryskoo.framework.core.bean.QueryMessageDTO;
import com.ryskoo.framework.core.utils.DateFormatHelper;
import com.ryskoo.framework.core.utils.ReflectHelper;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.persistence.Column;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class BasicJPAImpl<M extends BasicPO> implements IBasicRepository<M> {

    @Autowired
    private SessionFactory sessionFactory;

    protected SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    public Session getH4Session() {
        Session s = null;
        try {
            s = getSessionFactory().getCurrentSession();
        } catch (Exception err) {
            s = getSessionFactory().openSession();
        }
        return s;
    }

    @Override
    public void create(M m) {
        if (StringUtils.isBlank(m.getUuid())) {
            m.setUuid(BasicPO.genUuid());
        }
        m.setCreateTime(DateFormatHelper.getNowTimeStr());
        getH4Session().save(m);
    }

    @Override
    public void update(M m) {
        getH4Session().update(m);
    }

    @Override
    public void createOrUpdate(M m) {
        if (StringUtils.isBlank(m.getUuid())) {
            m.setUuid(BasicPO.genUuid());
        }
        getH4Session().saveOrUpdate(m);
    }

    @Override
    public void delete(M m) {
        getH4Session().delete(m);
    }

    @Override
    public M getByUuid(String uuid) {
        return (M) getH4Session().get(ReflectHelper.getModelClass(this.getClass()), uuid);
    }

    @Override
    public void deletes(List<String> needDeleteUuids) {
        String hql = "delete from " + ReflectHelper.getModelClass(this.getClass()).getName() + " o where o.uuid in (:uuids)";

        Map<String, Object> mapParams = new HashMap<String, Object>();
        mapParams.put("uuids", needDeleteUuids.toArray());

        this.exeUpdate(hql, mapParams);
    }

    @Override
    public List<M> getAll() {
        String hql = "select o" + getMultiSelect() + " from "
                + ReflectHelper.getModelClass(this.getClass()).getName()
                + " o " + getMultiModel() + " where 1=1 ";

        Query q = getH4Session().createQuery(hql);

        // 如果是符合对象，还需要再进行转换
        if (getMultiSelect() != null && getMultiSelect().trim().length() > 0) {
            List<Object[]> tempList = q.list();
            return exeResultList(tempList);
        }
        return q.list();
    }


    @Override
    public List<String> getAllUuids() {
        String hql = "select o.uuid" + getMultiSelect() + " from "
                + ReflectHelper.getModelClass(this.getClass()).getName()
                + " o " + getMultiModel() + " where 1=1 ";

        Query q = getH4Session().createQuery(hql);
        return q.list();
    }

    @Override
    public List<M> queryByCondition(QueryMessageDTO qm, boolean byPage) {
        String hql = "select o" + getMultiSelect() + " from "
                + ReflectHelper.getModelClass(this.getClass()).getName() + " o "
                + getMultiModel() + " where 1=1 ";

        hql += prepareHql(qm);
        hql += getAppendHql(qm);

        Query q = getH4Session().createQuery(hql);
        setValue(qm, q);
        setAppendHqlValue(qm, q);

        if (byPage) {
            q.setFirstResult(qm.getStartNum());
            q.setMaxResults(qm.getPageShow());
        }

        if (getMultiSelect() != null && getMultiSelect().trim().length() > 0) {
            List<Object[]> tempList = q.list();
            return exeResultList(tempList);
        }
        List<M> retList = q.list();
        return retList;
    }

    @Override
    public List<M> queryByNativeSql(QueryMessageDTO qm, boolean byPage) throws NoSuchFieldException {
        Class modelClass = ReflectHelper.getModelClass(this.getClass());
        String sql = "select " + getSelectFields(modelClass) + " from "
                + getTableName(modelClass) + " o "
                + getJoinTable() + " where 1=1 ";

        sql += prepareNativeSql(qm);
        sql += getAppendNativeSql(qm);

        Query q = getSqlQuery(sql, modelClass);
        setValue(qm, q);
        setAppendHqlValue(qm, q);

        if (byPage) {
            q.setFirstResult(qm.getStartNum());
            q.setMaxResults(qm.getPageShow());
        }

        /*if (getJoinTable() != null && getJoinTable().trim().length() > 0) {
            List<Object[]> tempList = q.list();
            return exeResultList(tempList);
        }*/
        List<M> retList = q.list();
        return retList;
    }

    protected String getSelectFields(Class clazz) throws NoSuchFieldException {
        return "o.*";
    }

    protected String getJoinTable() throws NoSuchFieldException {
        return "";
    }

    protected Query getSqlQuery(String sql, Class modelClass) {
        return getH4Session().createSQLQuery(sql).addEntity(modelClass);
    }

    protected String getTableName(Class clazz) {
        Table annotation = (Table) clazz.getAnnotation(Table.class);
        return annotation.name();
    }

    protected String getFieldName(Class clazz, String fieldName) throws NoSuchFieldException {
        // 如果当前类已是Object类，则直接抛出 NoSuchFieldException
        if (Object.class == clazz) {
            throw new NoSuchFieldException();
        }
        String name;
        try {
            Field field = clazz.getDeclaredField(fieldName);
            Column annotation = field.getAnnotation(Column.class);
            name = annotation.name();
        } catch (NoSuchFieldException e) {
            // 如果当前类没有找到属性，则递归到父类查找
            name = getFieldName(clazz.getSuperclass(), fieldName);
        }
        return name;
    }


    @Override
    public List<String> getUuidsByCondition(QueryMessageDTO qm, boolean byPage) {
        String hql = "select o.uuid from "
                + ReflectHelper.getModelClass(this.getClass()).getName() + " o "
                + getMultiModel() + " where 1=1 ";

        hql += prepareHql(qm);
        hql += getAppendHql(qm);

        Query q = getH4Session().createQuery(hql);
        setValue(qm, q);
        setAppendHqlValue(qm, q);

        if (byPage) {
            q.setFirstResult(qm.getStartNum());
            q.setMaxResults(qm.getPageShow());
        }
        return q.list();
    }

    @Override
    public int getCount(QueryMessageDTO qm) {
        String hql = "select count(o.uuid) from "
                + ReflectHelper.getModelClass(this.getClass()).getName()
                + " o " + getMultiModel() + " where 1=1 ";

        hql += prepareHql(qm);
        hql += getAppendHql(qm);

        Query q = getH4Session().createQuery(hql);
        setValue(qm, q);
        setAppendHqlValue(qm, q);

        return ((Number) q.uniqueResult()).intValue();
    }

    @Override
    public int getCountByNativeSql(QueryMessageDTO qm) throws NoSuchFieldException {
        String hql = "select count(o.uuid) from "
                + getTableName(ReflectHelper.getModelClass(this.getClass()))
                + " o " + getJoinTable() + " where 1=1 ";

        hql += prepareNativeSql(qm);
        hql += getAppendNativeSql(qm);

        Query q = getH4Session().createSQLQuery(hql);
        setValue(qm, q);
        setAppendHqlValue(qm, q);

        return ((Number) q.uniqueResult()).intValue();
    }

    @Override
    public void batchCreate(List<M> list) {
        int count = list.size();
        for (int i = 0; i < count; i++) {
            M obj = list.get(i);
            if (StringUtils.isBlank(obj.getUuid())) {
                obj.setUuid(BasicPO.genUuid());
            }
            obj.setCreateTime(DateFormatHelper.getNowTimeStr());
            getH4Session().save(obj);
            if ((i + 1) % 100 == 0) {
                getH4Session().flush();
            }
        }
    }

    @Override
    public void batchUpdate(List<M> list) {
        int count = list.size();
        for (int i = 0; i < count; i++) {
            getH4Session().update(list.get(i));
            if ((i + 1) % 100 == 0) {
                getH4Session().flush();
            }
        }
    }

    /**
     *
     * ========================================= 辅助执行的方法
     * =========================================
     *
     */
    /**
     * 用来辅助执行更新类的hql的方法
     *
     * @param hql       更新类的hql
     * @param paramsMap 要设置的参数
     */
    protected void exeUpdate(final String hql, final Map<String, Object> paramsMap) {
        Query q = getH4Session().createQuery(hql);

        for (String key : paramsMap.keySet()) {
            Object obj = paramsMap.get(key);
            if (obj instanceof Object[]) {
                q.setParameterList(key, (Object[]) obj);
            } else {
                q.setParameter(key, obj);
            }
        }
        q.executeUpdate();
    }

    /**
     * 如果需要从多个表中获取多项数据，这个方法用来拼接 select后面的选择字段
     *
     * @return 拼接好的select后面的选择字段 的字符串
     */
    protected String getMultiSelect() {
        return "";
    }

    /**
     * 如果需要从多个表中获取多项数据，这个方法用来拼接from后面的多个数据对象
     *
     * @return 拼接好的from后面的多个数据对象 的字符串
     */
    protected String getMultiModel() {
        return "";
    }

    /**
     * 如果需要从多个表中获取多项数据，这个方法用来执行对结果集的处理
     *
     * @param tempList 查询获得的数据
     * @return 把查询获得的数据，通过处理，转换成最终需要的数据
     */
    protected List<M> exeResultList(List<Object[]> tempList) {
        return null;
    }

    /**
     * 为Hql拼接条件
     *
     * @param qm 查询model
     * @return 拼接好的条件
     */
    protected String prepareHql(QueryMessageDTO qm) {
        StringBuilder hql = new StringBuilder();
        Map<String, Map<String, Object>> map = qm.getQueryParams();

        for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
            String field = entry.getKey();
            Map<String, Object> param = entry.getValue();
            Object value = MapUtils.getObject(param, "value");
            if (null != value && StringUtils.isNotBlank(value.toString())) {
                String operation = MapUtils.getString(param, "operation");
                //前台如果不传operation，表示要重写getAppendHql方法拼接sql语句
                if (StringUtils.isNotBlank(operation)) {
                	// 从页面传递过来的值，直接拼装，不再校验是否属性，提高性能
                	hql.append(" ");
                	addOneCondition(hql, field, operation);
                	hql.append(" ");
                }
            }
        }
        return hql.toString();
    }

    /**
     * 为Hql拼接一个条件表达式
     *
     * @param hql       HQL语句
     * @param name      字段名称
     * @param operation 条件的比较方式
     */
    private void addOneCondition(StringBuilder hql, String name, String operation) {
        switch (QueryConditionEnum.valueOf(operation)) {
            case NE:
                hql.append(" AND o.").append(name).append(" <> :").append(name);
                break;
            case EQ:
                hql.append(" AND o.").append(name).append(" = :").append(name);
                break;
            case LIKE:
            case LLIKE:
            case RLIKE:
                hql.append(" AND o.").append(name).append(" LIKE :").append(name);
                break;
            case GT:
                hql.append(" AND o.").append(name).append(" > :").append(name);
                break;
            case GE:
                hql.append(" AND o.").append(name).append(" >= :").append(name);
                break;
            case LT:
                hql.append(" AND o.").append(name).append(" < :").append(name);
                break;
            case LE:
                hql.append(" AND o.").append(name).append(" <= :").append(name);
                break;
            case IN:
                hql.append(" AND o.").append(name).append(" IN (:").append(name).append(")");
                break;
            case NOTIN:
                hql.append(" AND o.").append(name).append(" NOT IN (:").append(name).append(")");
                break;
            case BETWEEN:
                hql.append(" AND o.").append(name).append(" BETWEEN :").append(name).append("1 AND :").append(name).append("2");
                break;
            case OREQ:
                String[] eqArr = StringUtils.split(name, "#");
                List<String> eqList = new ArrayList<>();
                for (String str : eqArr) {
                    eqList.add("o." + str + " = :" + str);
                }
                hql.append(" AND (").append(StringUtils.join(eqList, " OR ")).append(")");
                break;
            case ORLIKE:
                String[] liekArr = StringUtils.split(name, "#");
                List<String> likeList = new ArrayList<>();
                for (String str : liekArr) {
                    likeList.add("o." + str + " LIKE :" + str);
                }
                hql.append(" AND (").append(StringUtils.join(likeList, " OR ")).append(")");
                break;
            default:
        }
    }

    protected String prepareNativeSql(QueryMessageDTO qm) throws NoSuchFieldException {
        StringBuilder hql = new StringBuilder();
        Map<String, Map<String, Object>> map = qm.getQueryParams();

        for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
            String field = entry.getKey();
            Map<String, Object> param = entry.getValue();
            Object value = MapUtils.getObject(param, "value");
            if (null != value && StringUtils.isNotBlank(value.toString())) {
                String operation = MapUtils.getString(param, "operation");
                // 从页面传递过来的值，直接拼装，不再校验是否属性，提高性能
                hql.append(" ");
                addNativeSqlCondition(hql, field, operation);
                hql.append(" ");
            }
        }
        return hql.toString();
    }

    private void addNativeSqlCondition(StringBuilder hql, String name, String operation) throws NoSuchFieldException {
        Class modelClass = ReflectHelper.getModelClass(this.getClass());
        switch (QueryConditionEnum.valueOf(operation)) {
            case NE:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" <> :").append(name);
                break;
            case EQ:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" = :").append(name);
                break;
            case LIKE:
            case LLIKE:
            case RLIKE:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" LIKE :").append(name);
                break;
            case GT:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" > :").append(name);
                break;
            case GE:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" >= :").append(name);
                break;
            case LT:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" < :").append(name);
                break;
            case LE:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" <= :").append(name);
                break;
            case IN:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" IN (:").append(name).append(")");
                break;
            case NOTIN:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" NOT IN (:").append(name).append(")");
                break;
            case BETWEEN:
                hql.append(" AND o.").append(getFieldName(modelClass, name)).append(" BETWEEN :").append(name).append("1 AND :").append(name).append("2");
                break;
            case OREQ:
                String[] eqArr = StringUtils.split(name, "#");
                List<String> eqList = new ArrayList<>();
                for (String str : eqArr) {
                    eqList.add("o." + getFieldName(modelClass, str) + " = :" + str);
                }
                hql.append(" AND (").append(StringUtils.join(eqList, " OR ")).append(")");
                break;
            case ORLIKE:
                String[] liekArr = StringUtils.split(name, "#");
                List<String> likeList = new ArrayList<>();
                for (String str : liekArr) {
                    likeList.add("o." + getFieldName(modelClass, str) + " LIKE :" + str);
                }
                hql.append(" AND (").append(StringUtils.join(likeList, " OR ")).append(")");
                break;
            default:
        }
    }

    /**
     * 如果需要从多个表中获取多项数据，这个方法用来拼接多表连接后的条件，还有排序等
     *
     * @param qm 查询model
     * @return 拼接多表连接后的条件的字符串
     */
    protected String getAppendHql(QueryMessageDTO qm) {
        if (qm != null) {
            Map<String, String> sortMap = qm.getSortMap();
            if (sortMap != null) {
                for (Map.Entry<String, String> entry : sortMap.entrySet()) {
                    String sortName = entry.getKey();
                    String sortValue = entry.getValue();
                    if (StringUtils.isNotBlank(sortName)
                            && StringUtils.isNotBlank(sortValue)) {
                        return "order by o." + sortName + " " + sortValue;
                    }

                }
            }

        }
        return "order by o.createTime desc ";
    }

    /**
     * 给原生SQL查询设置排序
     * @param qm
     * @return
     * @throws NoSuchFieldException
     */
    protected String getAppendNativeSql(QueryMessageDTO qm) throws NoSuchFieldException {
        if (qm != null) {
            Map<String, String> sortMap = qm.getSortMap();
            if (sortMap != null) {
                Class modelClass = ReflectHelper.getModelClass(this.getClass());
                for (Map.Entry<String, String> entry : sortMap.entrySet()) {
                    String sortName = entry.getKey();
                    String sortValue = entry.getValue();
                    if (StringUtils.isNotBlank(sortName) && StringUtils.isNotBlank(sortValue)) {
                        return "order by o." + getFieldName(modelClass, sortName) + " " + sortValue;
                    }

                }
            }

        }
        return "order by o.create_time desc ";
    }

    /**
     * 给查询条件赋值，是根据对象里面的MapCondition来执行的
     *
     * @param qm 查询model
     * @param q  Hibernate的查询对象
     */
    protected void setValue(QueryMessageDTO qm, Query q) {
        Map<String, Map<String, Object>> map = qm.getQueryParams();

        for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
            String field = entry.getKey();
            Map<String, Object> param = entry.getValue();
            Object value = MapUtils.getObject(param, "value");
            if (null != value && StringUtils.isNotBlank(value.toString())) {
                String operation = MapUtils.getString(param, "operation");
                if (StringUtils.isNotBlank(operation)) {
                	this.setOneConditionValue(q, field, value, operation);
                }
            }
        }
    }

    /**
     * 为某个条件字段设置比较的条件数据
     *
     * @param query     Hibernate的查询对象
     * @param name      字段名字
     * @param value     查询条件的值
     * @param operation 条件的比较方式，值从ConditionOpTypeEnum中来
     */
    private void setOneConditionValue(Query query, String name, Object value, String operation) {
        switch (QueryConditionEnum.valueOf(operation)) {
            case NE:
                if (value instanceof String) {
                    query.setString(name, String.valueOf(value));
                } else if (value instanceof Number || value instanceof Boolean) {
                    query.setParameter(name, value);
                }
                break;
            case EQ:
                if (value instanceof String) {
                    query.setString(name, String.valueOf(value));
                } else if (value instanceof Number || value instanceof Boolean) {
                    query.setParameter(name, value);
                }
                break;
            case LIKE:
                query.setString(name, "%" + value + "%");
                break;
            case LLIKE:
                query.setString(name, "%" + value);
                break;
            case RLIKE:
                query.setString(name, value + "%");
                break;
            case GT:
                if (value instanceof String) {
                    query.setString(name, String.valueOf(value));
                } else if (value instanceof Number) {
                    query.setParameter(name, value);
                }
                break;
            case GE:
                if (value instanceof String) {
                    query.setString(name, String.valueOf(value));
                } else if (value instanceof Number) {
                    query.setParameter(name, value);
                }
                break;
            case LT:
                if (value instanceof String) {
                    query.setString(name, String.valueOf(value));
                } else if (value instanceof Number) {
                    query.setParameter(name, value);
                }
                break;
            case LE:
                if (value instanceof String) {
                    query.setString(name, String.valueOf(value));
                } else if (value instanceof Number) {
                    query.setParameter(name, value);
                }
                break;
            case IN:
                if (value instanceof List) {
                    query.setParameterList(name, (List) value);
                }
                break;
            case NOTIN:
                if (value instanceof List) {
                    query.setParameterList(name, (List) value);
                }
                break;
            case BETWEEN:
                if (value instanceof List && ((List) value).size() > 1) {
                    query.setParameter(name + "1", ((List) value).get(0));
                    query.setParameter(name + "2", ((List) value).get(1));
                }
                break;
            case OREQ:
                String[] eqArr = name.split("#");
                for (String str : eqArr) {
                    if (value instanceof String) {
                        query.setString(str, String.valueOf(value));
                    } else if (value instanceof Number) {
                        query.setParameter(str, value);
                    }
                }
                break;
            case ORLIKE:
                String[] likeArr = name.split("#");
                for (String str : likeArr) {
                    query.setString(str, "%" + value + "%");
                }
                break;
            default:
        }
    }

    /**
     * 如果需要从多个表中获取多项数据，这个方法用来为拼接多表连接后的条件 赋条件值
     *
     * @param qm 查询model
     * @param q  Hibernate的查询对象
     */
    protected void setAppendHqlValue(QueryMessageDTO qm, Query q) {
    }

    /**
     * 从查询Model中获取某个字段的条件值
     *
     * @param name 字段名字
     * @param qm   查询model
     * @return 查询Model中对应的某个字段的条件值
     */
    private Object getOneFiledValue(String name, QueryMessageDTO qm) {
        Object[] objs = this.getFieldAndMethod(name, qm);
        Field f = (Field) objs[0];
        Method mth = (Method) objs[1];

        Object value = null;
        try {
            value = mth.invoke(qm);
        } catch (Exception e) {
            // e.printStackTrace();
        }
        return value;
    }

    /**
     * 获取某个字段对应的field对象和相应的get方法
     *
     * @param name 某个字段的名字
     * @param qm   查询model
     * @return 该字段对应的field对象和相应的get方法
     */
    private Object[] getFieldAndMethod(String name, QueryMessageDTO qm) {
        Field f = null;
        Method mth = null;
        try {
            f = qm.getClass().getDeclaredField(name);
            mth = qm.getClass().getDeclaredMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
        } catch (Exception e) {
            //
        }
        if (f == null) {
            try {
                f = qm.getClass().getSuperclass().getDeclaredField(name);
                mth = qm.getClass()
                        .getSuperclass()
                        .getDeclaredMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
            } catch (Exception e) {
                //
            }
        }
        if (f == null) {
            try {
                f = qm.getClass().getSuperclass().getSuperclass().getDeclaredField(name);
                mth = qm.getClass()
                        .getSuperclass()
                        .getSuperclass()
                        .getDeclaredMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
            } catch (Exception e) {
                //
            }
        }
        return new Object[]{f, mth};
    }


    /**
     * 游离态更新/新增实体
     *
     * @param m
     */
    @Override
    public void merge(M m) {
        getH4Session().merge(m);
    }
}
